/*
* 四川生学教育科技有限公司
* Copyright (c) 2015-2025 Founder Ltd. All Rights Reserved.
*
* This software is the confidential and proprietary information of
* Founder. You shall not disclose such Confidential Information
* and shall use it only in accordance with the terms of the agreements
* you entered into with Founder.
*
*/
package cn.demo.zookeeper.lock;

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.data.Stat;


/**
 * @author WUANG (wa@sxw.cn)
 * @description https://gitbook.cn/books/5a8c91eec41e7374bbb4ea6a/index.html
 * @date 2018/11/1 17:20
 * @slogon 站在巨人的肩膀上
 * @since 2.0.0
 */
public class Locker {
    private CuratorFramework zkClient;

    private final String LOCKER = "/lock";

    private final Integer conditionVariable = 0;

    private Boolean locked = false;

    public Locker(CuratorFramework zkClient) {
        this.zkClient = zkClient;
    }

    //
    // 获取 “互斥锁”（阻塞）:我想既然方法里用了synchronized，方法也不用声明synchronized了；两个声明一个即可；另外应该改为static的，让这个东西直接成为一个工具类（不然单机上要建一个工具类单独来包装这个locker，并且全局维护一个locker对象）
    //
    public synchronized void lock() throws Exception {
        assert !locked;

        while (true) {
            try {
                this.zkClient.create().withMode(CreateMode.EPHEMERAL).forPath(LOCKER, "lock".getBytes());

                //
                // lock acquired
                //
                locked = true;
                return;
            } catch (KeeperException exception) {
                if (exception.code() == KeeperException.Code.NODEEXISTS) {
                    //
                    // lock acquire by others
                    //
                } else {
                    throw exception;
                }
            }

            Stat stat = this.zkClient.checkExists().usingWatcher((CuratorWatcher) (event) -> {
                if (event.getType() == EventType.NodeDeleted) {
                    synchronized (conditionVariable) {
                        conditionVariable.notify();
                    }
                }
            }).forPath(LOCKER);

            if (stat != null) {
                //
                // lock NOT released, block ...
                //
                synchronized (conditionVariable) {
                    conditionVariable.wait();
                }
            }
        }
    }

    //
    // 释放 “锁”
    //
    public synchronized void unLock() throws Exception {
        assert locked;

        this.zkClient.delete().forPath(LOCKER);
        locked = false;
    }
}